/*
Another game of life with SDL renderer.
Copyright (C) 2009-2010  Julien CLEMENT (andematunum@gmail.com)
*/
/*
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Matrix.h"
#include "SDLMatrixRenderer.h"

#include <iostream> // For cout, endl
#include <stdlib.h> // For EXIT codes, strtoul, atexit
#include <string.h> // For strtok
#include <errno.h>

using namespace std;

extern int errno;

// ///////////////////////////////
// COMMAND LINE PARSING MANAGEMENT
// ///////////////////////////////
typedef struct {
	unsigned int r,g,b;
} RGBTriplet;

typedef struct {
	unsigned int Mw;     // Matrix width
	unsigned int Mh;     // Matrix height
	unsigned int Rx;     // X resolution (renderer)
	unsigned int Ry;     // Y resolution (renderer)
	
	bool usecolors;      // Color version
	bool wrap;           // Wrap cells on borders
	bool step;           // Step mode: wait for key press for next step
	bool fastest;        // Don't do any delay
	bool fullscreen;     // Fullscreen mode

	unsigned int delayms; // Delay in milliseconds in non-step mode
    RGBTriplet rgb;		  // RGB color of alive cells in non-color version
} Input;

// Default values
Input input = {
	256,
	256,
	2,
	2,

	false,
	true,
	false,
	false,
	false,
	
	10,
	{ 255, 255, 255}
};

typedef enum {
	UINT,
	BOOL,
	RGB
} OptionType;

typedef struct {
	OptionType type;  // Option type
	const char *name; // Option string
	const char *help; // Option help
	void *data;       // Associated input data
} Option;

#define NOPTIONS (11)
Option options[NOPTIONS] = {
	{ UINT, "--mw", "[n] Life matrix width (number of cells in width)",	&input.Mw   },
	{ UINT, "--mh", "[n] Life matrix height (number of cells in height)",	&input.Mh	},
	{ UINT, "--rx", "[n] Width of a cell on display",						&input.Rx	},
	{ UINT, "--ry", "[n] Height of a cell on display",					&input.Ry	},
	
	{ BOOL, "--color", "Color version of Game Of Life (experimental)",&input.usecolors },
	{ BOOL, "--nowrap",  "Don't wrap edge cells",								&input.wrap },
	{ BOOL, "--step",  "Step mode: wait for keypress at each step",   &input.step },
	{ BOOL, "--fastest", "Don't perform any delay between each step", &input.fastest },
	{ BOOL, "--fullscreen", "Fullscreen mode", &input.fullscreen },

	{ UINT, "--delayms", "[n] Delay in milliseconds between each step",   &input.delayms   },
	{ RGB, "--rgb", "[r,g,b] A comma separated RGB triplet with each component in [0,255] for the colors of alive cells (non-color version)",
			&input.rgb }
};

void help (const char *progname) {
	printf (
	"\n"
	"%s help\n"
	"\n"
	"Syntax: %s [options]\n"
	"Where [options] is a list of possible options, amongst:\n"
	"\n",
	progname, progname
	);
	for (int k=0; k < NOPTIONS; k++) {
		printf ("\033[5G%s\033[20G%s\n", options[k].name, options[k].help);
	}
	printf ("\033[5G-h|--help\033[20GDisplay this help screen\n");
	printf ("\n");
}

bool parse_uint (unsigned int *rop, char *op) {
	char *syntax_error;

	// Forbid negative integers
	if (op[0] == '-') {
		printf ("Expected a positive value: %s\n", op);
		return false;
	}

	*rop = strtoul(op, &syntax_error, 10);
	if (*syntax_error != '\0') {
		printf ("Syntax error: %c\n", *syntax_error, *syntax_error);
		return false;
	}
	switch (errno) {
		case EINVAL:
			printf ("Invalid argument: %s (a base 10 unsigned integer was expected)\n", op);
			return false;
			break;
		case ERANGE:
			printf ("Value out of range: %s\n", op);
			return false;
			break;
		default:
			break;
	}
	return true;
}

bool parse_opt (Option* opt, int * argc, char *** argv) {
	switch (opt->type) {
		case BOOL:
		{
			bool *flag = (bool *)opt->data;
			(*flag) = ! (*flag);
			break;
		}
		case UINT:
		{
			(*argv)++;
			(*argc)--;
			unsigned int *uint = (unsigned int *)opt->data;
			if (!parse_uint(uint, **argv)) {
				return false;
			}
			break;
		}
		case RGB:
		{
			(*argv)++;
			(*argc)--;
			char *r_opt = strtok(**argv,",");
			if (r_opt == NULL) {
				printf ("Parse error: red component not found\n");
				return false;
			}
			if (!parse_uint(&((RGBTriplet *)opt->data)->r, r_opt)) {
				return false;
			}

			char *g_opt = strtok(NULL,",");
			if (g_opt == NULL) {
				printf ("Parse error: green component not found\n");
				return false;
			}
			if (!parse_uint(&((RGBTriplet *)opt->data)->g, g_opt)) {
				return false;
			}

			char *b_opt = strtok(NULL,",");
			if (b_opt == NULL) {
				printf ("Parse error: blue component not found\n");
				return false;
			}
			if (!parse_uint(&((RGBTriplet *)opt->data)->b, b_opt)) {
				return false;
			}
			break;
		}
		default:
		{
			printf ("Option type not recognized (code = %d)\n", opt->type);
			return false;
			break;
		}
	}
	return true;
}

bool parse_args (int argc, char ** argv) {
	// Read options (if any)
	int k;
	char *progname = argv[0];

	while (--argc) {
		argv++;
		// Special option: help (-h | --help)
		if ((!strcmp(*argv,"-h")) || (!strcmp(*argv,"--help"))) {
			// help and quit
			help(progname);
			return false;
		}
		// Find the option
		bool found;
		for (k=0; k < NOPTIONS; k++) {
			if ( !strcmp(*argv,options[k].name) ) {
				if (parse_opt(&options[k], &argc, &argv)) {
					break;
				}
				else {
					// Parse error: help and quit
					help(progname);
					return false;
				}
			}
		}

		if (k == NOPTIONS) {
			// Option not found: help and quit
			printf ("!! Bad option: %s\n", *argv);
			help(progname);
			return false;
		}
	}
	return true;
}

int main (int argc, char ** argv) {

	SDL_Event evt;
	Uint32 timestart, timeend;
	long count_frames = 0;
	double fps = 0.0;

	if (parse_args(argc, argv) == false) {
		return (EXIT_SUCCESS);
	}
	
	SDL_Init(SDL_INIT_VIDEO);
	atexit(SDL_Quit);

	unsigned int resx = input.Mw * (1+input.Rx); // one pixel padding
	unsigned int resy = input.Mh * (1+input.Ry); // one pixel padding
	
	Uint32 flags = SDL_HWSURFACE;

	if (input.fullscreen) {
		flags |= SDL_FULLSCREEN;
	}

	if (SDL_SetVideoMode(resx, resy, 0, flags) == NULL) {
		cout << "Couldn't setup a " << resx << "x" << resy << " display, please change the cell resolution (Mx and My switches)" << endl;
		return (EXIT_FAILURE);
	}

	srand(time(0));
	
	Matrix matrix(input.Mw, input.Mh, input.wrap);
	SDLMatrixRenderer renderer(&matrix, input.Rx, input.Ry, input.rgb.r, input.rgb.g, input.rgb.b);

	if (input.usecolors) {
		matrix.initRandomColor();
	} else {
		matrix.initRandom();
	}
	//matrix.initGlider(0,0);
	//matrix.initGlider(11,11);
	//matrix.initGlider(5,0);
	//matrix.initOscillator(32,32);
	//matrix.initOscillator(16,16);

	renderer.render();

	count_frames = 0;
	int done     = 0;
	timestart = SDL_GetTicks();
	for ( ; !done; ) {

		//if (!(count_frames  % 500)) {
		if (((timeend = SDL_GetTicks()) - timestart) > 5000) {
			if (count_frames > 0) {
			 //timeend = SDL_GetTicks();
			 fps = 1000.0 * (double)count_frames / ((double)timeend - (double)timestart);
			 cout << "FPS: " << fps << endl;
			 count_frames = 0;
			 timestart = timeend;
			}
		}

		// Next step of evolution
		bool evolved = matrix.life();

		if (!evolved) {
			cout << "No more evolution is possible" << endl;
			break;
		}

		renderer.render();
		matrix.swap();

		// Detect quit requests (close window or Escape key pressed)
		while (SDL_PollEvent(&evt) > 0) {
			if ((evt.type == SDL_KEYDOWN) &&
				(evt.key.keysym.sym == SDLK_ESCAPE)) {
				done = 1;
				break;
			}
			if (evt.type == SDL_QUIT) {
				done = 1;
				break;
			}
		}

		// Not step mode ? Make a pause
		if (!input.step) {
			if ((input.fastest == false) && (input.delayms > 0)) {
				SDL_Delay(input.delayms);
			}
		}
		// Step mode ? Wait for keypress
		else {
			for (;;) {
				SDL_WaitEvent(&evt);
				if (evt.type == SDL_KEYDOWN) {
					if (evt.key.keysym.sym != SDLK_ESCAPE) {
						break;
					} else {
						done = 1;
						break;
					}
				}
			}
		}
		count_frames ++;
	}

	timeend = SDL_GetTicks();
	fps = 1000.0 * (double)count_frames / ((double)timeend - (double)timestart);
	cout << "FPS: " << fps << endl;

	// Not interrupted by the user
	if (!done) {
		cout << endl;
		cout << "Simulation finished, press Escape to exit" << endl;
		cout << endl;

		for (;;) {
			SDL_WaitEvent(&evt);
			if ((evt.type == SDL_KEYDOWN) && (evt.key.keysym.sym == SDLK_ESCAPE)) {
				break;
			}
		}
	}

	return (0);
}

